<?php
$config = array(
    'appKey' => '2c2dacb6facf452b881df8210e3853d5',
    //折淘客Appkey，http://www.zhetaoke.com/user/open/open_appkey.aspx获取
    'pageSize' => 50,
    //关键词获取内容数(范围 ： 1-50)
    'GoodsCache' => 60,
    //关键词内容缓存时间(单位 ：秒)(0为不缓存)
    'keywordCache' => 60,
    //相关关键词内容缓存时间(单位 ：秒)(0为不缓存)
    'ContentCache' => 60,
    //商品内容缓存时间(单位 ：秒)(0为不缓存)
    'Url' => array(
        'listUrlExample' => '/tb{keyword}/',
        //关键词URL格式，({keyword} : 代表关键词标识)
        'listUrlFormat' => 0,
        //关键词URL中{keyword}标识的格式，(0 ： 纯数字，1：纯字母，2：字母数字混合，3：关键词中文字)
        'listUrlLength' => 8,
        //关键词URL中{keyword}标识的长度（注：标识的格式为3时无效）
        'contentUrlExample' => '/tk{keyword}i{content}.html' //内容URL格式，({keyword} : 代表关键词标识，{content} ： 代表商品ID)
    ),
    //以上URL中需伪静态配合
    'isSaveKeyword' => 1,
    //相关关键词是否加入keyword.txt中
    'cachePath' => 'cache2',
    //缓存目录（以当前目录为基础）
    'baiDuSite' => 'quan618.cn',
    //百度提交站点域名
    'baiDuToken' => '9RPiouBbV8iVvhMr',
    //百度提交站点Token
    'baiDuNum' => 10,
    //每次推送条数
    'isMip' => false //是否开启MIP推送
);

//推送地址为 index.php?a=submit
//xml地图地址为 index.php?a=sitemap

$app = new App();

$app->start($config);

/**
 * 程序主类
 * Class App
 */
class App
{
    private $Cache = null;

    private $listUrl = null;

    private $localKeyword = array();

    private $newKeyword = array();

    private $config = array();

    public function start($config = array())
    {
        header("Content-type: text/html; charset=utf-8");

        define('DS', DIRECTORY_SEPARATOR);
        define('ROOT', dirname(__FILE__) . DS);

        $this->config = $config;

        $cachePath = ROOT . $this->config['cachePath'];
        if (!is_dir($cachePath))
            @mkdir($cachePath, 0777);
        $this->Cache = new Cache($cachePath);

        $this->listUrl = $this->Cache->get('listUrl');
        $this->listUrl = empty($this->listUrl) ? array() : $this->listUrl;
        $this->newKeyword = $this->Cache->get('newKeyword');
        $this->newKeyword = empty($this->newKeyword) ? array() : $this->newKeyword;

        $txt = file_get_contents(ROOT . 'keywords.txt');
        if (!empty($txt)) {
            $this->localKeyword = strpos($txt, "\r\n") === false ? explode("\n", $txt) : explode("\r\n", $txt);
            $this->localKeyword = array_filter($this->localKeyword);
            $this->localKeyword = array_unique($this->localKeyword);
        }

        list($action, $keyword) = $this->route();
        if ($action == 'index'){
            //这里获取热卖商品渲染为首页
            $this->setList($keyword);
        }
        if ($action == 'list') {
            $this->setList($keyword);
        } else if ($action == 'content') {
            $this->setContent($keyword);
        } else if ($action == 'sitemap') {
            $this->setSitemap();
        } else if ($action == 'submit') {
            $this->setSubmit();
        } else if ($action == '404') {
            $content = file_get_contents(ROOT . 'error' . DS . '404.html');
            echo $content;
        }

    }

    private function setSubmit()
    {
        $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $baseUrl = $protocol . $_SERVER['HTTP_HOST'];

        $submitLog = $this->Cache->get('submitLog');
        $submitLog = !empty($submitLog) ? $submitLog : array();

        $submitUrl = 'http://data.zz.baidu.com/urls?site=' . $this->config['baiDuSite'] . '&token=' . $this->config['baiDuToken'];
        if ($this->config['isMip'])
            $submitUrl .= '&type=mip';

        $urls = array();
        $i = 0;
        foreach ($this->listUrl as $key => $value) {
            $url = $baseUrl . str_replace('{keyword}', $value, $this->config['Url']['listUrlExample']);
            if (!in_array($url, $submitLog)) {
                $urls[] = $url;
                $submitLog[] = $url;
                $i++;
                if ($i == $this->config['baiDuNum']) {
                    break;
                }
            }
        }

        if (empty($urls))
            exit('暂无新Url推荐');

        $ch = curl_init();
        $options = array(
            CURLOPT_URL => $submitUrl,
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POSTFIELDS => implode("\n", $urls),
            CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
        );
        curl_setopt_array($ch, $options);
        $result = curl_exec($ch);

        $result = json_decode($result);

        if ($result->error) {
            exit('推送错误！错误描述：' . $result->message);
        } else {
            $html = '<style>h3{ margin-block-start : 0rem ; margin-block-end : 0rem ;}</style><h2>推送成功！</h2>';
            $html .= '<h3>本次推送条数 :  <font color="red">' . count($urls) . '</font></h3>';
            $html .= '<h3>成功条数 :  <font color="red">' . $result->success . '</font></h3>';
            $html .= '<h3>剩余推送量 ：  <font color="blue">' . $result->remain . '</font></h3>';
            if (isset($result->not_same_site)) {
                $html .= '<h3>由于不是本站url而未处理的url ：  <br><font color="#8a2be2">' . implode("<br> ", $result->not_same_site) . '</font></h3>';
            }
            if (isset($result->not_valid)) {
                $html .= '<h3>不合法的url ：  <br><font color="fuchsia">' . implode("<br> ", $result->not_valid) . '</font></h3>';
            }
            if ($result->success == count($urls)) {
                $this->Cache->set('submitLog', $submitLog);
            }
            exit($html);
        }
    }

    /**
     * 生成百度地址
     */
    private function setSitemap()
    {
        $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $baseUrl = $protocol . $_SERVER['HTTP_HOST'];

        header('Content-type: text/xml');
        $xml = '<?xml version="1.0" encoding="UTF-8"?><urlset>';
        foreach ($this->listUrl as $key => $value) {
            $url = $baseUrl . str_replace('{keyword}', $value, $this->config['Url']['listUrlExample']);
            $xml .= "<url>\n";
            $xml .= "<loc>" . $url . "</loc>\n";
            $xml .= "<lastmod>" . date('Y-m-d') . "</lastmod>\n";
            $xml .= "<changefreq>daily</changefreq>\n";
            $xml .= "<priority>0.8</priority>\n";
            $xml .= "</url>\n";
        }
        $xml .= '</urlset>';
        exit($xml);
    }

    /**
     * 渲染内容
     * @param string $itemId
     */
    private function setContent($itemId = '')
    {
        $k = isset($_GET['k']) && !empty($_GET['k']) ? $_GET['k'] : '';
        $k = rtrim(trim($k, '/'), '/');
        $GoodsInfo = $this->getContent($itemId);
        // 搜索关键词逻辑上查明原因
        $keyword = array_search($k, $this->listUrl);
        //这里若为false 使用网络查讯
        if ($keyword == false) {
            $keywordArr = $this->getKeyword($k);
            // var_dump($keywordArr);
        }

        $content = file_get_contents(ROOT . 'static' . DS . 'content.html');

        //处理下一页数据
        $allData = $this->getGoods($keyword);

        $next = "";
        $prev = "<a href='#'><<返回</a>";

        foreach ($allData as $key => $value) {
            if ($value['itemId'] == $itemId) {
                if (isset($allData[$key + 1])) {
                    $next = "<a href='" . $this->getContentUrl($allData[$key + 1]['itemId'], $keyword) . "'>下一页</a>";
                }
                if (isset($allData[$key - 1])) {
                    $prev = "<a href='" . $this->getContentUrl($allData[$key - 1]['itemId'], $keyword) . "'>上一页</a>";
                }
            }
        }

        $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $baseUrl = $protocol . $_SERVER['HTTP_HOST'];
        $replaces = [
            '{商品ID}' => $itemId,
            '{商品标题}' => $GoodsInfo->tao_title,
            '{缩略图}' => $GoodsInfo->pict_url,
            '{店家名称}' => $GoodsInfo->shop_title,
            '{收藏数量}' => $GoodsInfo->favcount,
            '{评论数}' => $GoodsInfo->commentCount,
            '{当前链接}' => $baseUrl . $this->getContentUrl($itemId, $keyword),
            '{当前关键词链接}' => $this->getListUrl($keyword),
            '{当前关键词}' => $keyword,
            '{上一页}' => $prev,
            '{下一页}' => $next,
        ];
        $content = str_replace(array_keys($replaces), array_values($replaces), $content);

        echo $content;
    }

    /**
     * 渲染列表
     * @param string $keyword
     */
    private function setList($keyword = '')
    {
        $keywords = $this->getKeyword($keyword);
        $goods = $this->getGoods($keyword);

        $content = file_get_contents(ROOT . 'static' . DS . 'list.html');

        //相关词
        if (preg_match_all('/{相关关键词}(.*?){\/相关关键词}/is', $content, $all, PREG_SET_ORDER)) {
            foreach ($all as $match) {
                $html = '';
                foreach ($keywords as $key => $value) {
                    $replaces = array('{链接}' => $this->getListUrl($value), '{关键词}' => $value, '{序号}' => ($key + 1));
                    $html .= str_replace(array_keys($replaces), array_values($replaces), $match[1]);
                }
                $content = str_replace($match[0], $html, $content);
            }
        }

        //随机词
        if (preg_match_all('/{随机关键词(.*?)}/is', $content, $all, PREG_SET_ORDER)) {
            $count = count($all);
            $localKeyword = $this->getLocalKeyword($count);
            foreach ($all as $key => $value) {
                $skeyword = isset($localKeyword[$key]) ? $localKeyword[$key] : '';

                $replaces = array(
                    '{随机链接' . $value[1] . '}' => (!empty($skeyword) ? $this->getListUrl($skeyword) : ''),
                    $value[0] => $skeyword
                );
                $content = str_replace(array_keys($replaces), array_values($replaces), $content);
            }
        }

        //商品
        if (preg_match_all('/{商品列表}(.*?){\/商品列表}/is', $content, $all, PREG_SET_ORDER)) {
            foreach ($all as $match) {
                $html = '';
                foreach ($goods as $key => $value) {
                    $replaces = array(
                        '{链接}' => $value['Url'],
                        '{标题}' => $value['title'],
                        '{缩略图}' => $value['image'],
                        '{简介}' => $value['desc'],
                        '{序号}' => ($key + 1)
                    );
                    $html .= str_replace(array_keys($replaces), array_values($replaces), $match[1]);
                }
                $content = str_replace($match[0], $html, $content);
            }
        }

        //最近更新
        if (preg_match_all('/{最近更新}(.*?){\/最近更新}/is', $content, $all, PREG_SET_ORDER)) {
            $newKeyword = $this->newKeyword;
            krsort($newKeyword);
            $newKeyword = array_values($newKeyword);
            foreach ($all as $match) {
                $html = '';
                foreach ($newKeyword as $key => $value) {
                    $replaces = array('{链接}' => $this->getListUrl($value), '{关键词}' => $value, '{序号}' => ($key + 1));
                    $html .= str_replace(array_keys($replaces), array_values($replaces), $match[1]);
                }
                $content = str_replace($match[0], $html, $content);
            }
        }

        $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $baseUrl = $protocol . $_SERVER['HTTP_HOST'];

        $replaces = array(
            '{当前链接}' => $baseUrl . $this->getListUrl($keyword),
            '{当前关键词}' => $keyword
        );
        $content = str_replace(array_keys($replaces), array_values($replaces), $content);

        exit($content);
    }

    /**
     * 简单路由器
     * @return array
     */
    private function route()
    {
        function getParams($a, $b)
        {
            return array($a, $b);
        }

        $action = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : 'index';

        if ($action == 'list') {

            $k = isset($_GET['k']) && !empty($_GET['k']) ? $_GET['k'] : '';
            $k = rtrim(trim($k, '/'), '/');

            if ($keyword = array_search($k, $this->listUrl)) {
                return getParams('list', $keyword);
            }

            $keyword = $this->getLocalKeyword(1);
            return getParams('list', $keyword[0]);
        } else if ($action == 'content') {

            $c = isset($_GET['c']) && !empty($_GET['c']) ? $_GET['c'] : '';
            $c = rtrim(trim($c, '/'), '/');

            return getParams('content', $c);
        } else if ($action == 'sitemap') {
            return getParams('sitemap', '');

        } else if ($action == 'submit') {
            return getParams('submit', '');
        } else if ($action == '404') {

            $k = parse_url($this->getpageurl());
            return getParams($action, $k);
        }
        $keyword = $this->getLocalKeyword(1);

        return getParams('index', $keyword[0]);

    }

    /**
     * 获取页面的url
     * @return string
     */

    function getpageurl()
    {
        $pageURL = 'http';
        if (isset($_SERVER['HTTPS']) && $_SERVER["HTTPS"] == "on") {
            $pageURL .= "s";
        }
        $pageURL .= "://";
        if ($_SERVER["SERVER_PORT"] != "80") {
            $pageURL .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
        } else {
            $pageURL .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
        }
        return $pageURL;
    }

    /**
     * 获取本地的关键词
     * @param null|number $num 获取个数
     * @return array
     */
    private function getLocalKeyword($num = null)
    {
        $keywords = array();

        if ($num == null)
            return $this->localKeyword;

        $mt = array_rand($this->localKeyword, (count($this->localKeyword) >= $num ? $num : count($this->localKeyword)));

        if ($num == 1)
            return array($this->localKeyword[$mt]);

        foreach ($mt as $key => $value) {
            $keywords[] = $this->localKeyword[$value];
        }

        return $keywords;
    }

    /**
     * 商品内页
     * @param $itemId
     * @param $keyword
     * @return mixed|string
     */
    private function getContentUrl($itemId, $keyword)
    {

        $url = str_replace('{keyword}', $keyword, $this->config['Url']['contentUrlExample']);

        $url = str_replace('{content}', $itemId, $url);

        return $url;
    }


    /**
     * 获取一个关键词的Url
     * @param string $keyword
     * @return mixed
     */
    private function getListUrl($keyword = '')
    {
        $str = $keyword;

        if (isset($this->listUrl[$keyword])) {

            $str = $this->listUrl[$keyword];
            return str_replace('{keyword}', $str, $this->config['Url']['listUrlExample']);

        } else if ($this->config['Url']['listUrlFormat'] != 3) {

            while (true) {
                $str = $this->random($this->config['Url']['listUrlLength'], $this->config['Url']['listUrlFormat']);
                if (!array_search($str, $this->listUrl))
                    break;
            }
        }

        //生成链接
        $keywordUrl = str_replace('{keyword}', $str, $this->config['Url']['listUrlExample']);
        //保存链接
        $this->listUrl[$keyword] = $str;

        $this->Cache->set('listUrl', $this->listUrl);

        return $keywordUrl;
    }

    /**
     * 生成随机字符
     * @param int $length 长度
     * @param int $type 类型
     * @return string
     */
    private function random($length = 6, $type = 0)
    {
        $config = array(
            0 => '1234567890',
            1 => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
            2 => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
        );

        if (!isset($config[$type]))
            $type = 'string';
        $string = $config[$type];

        $code = '';
        $strlen = strlen($string) - 1;
        for ($i = 0; $i < $length; $i++) {
            $code .= $string[mt_rand(0, $strlen)];
        }

        if (!empty($convert)) {
            $code = ($convert > 0) ? strtoupper($code) : strtolower($code);
        }
        return $code;
    }

    /**
     * 获取云相关关键词
     * @param string $keyword
     * @return array
     */
    private function getKeyword($keyword = '')
    {
        if ($this->config['keywordCache'] > 0) {
            $data = $this->Cache->get(md5('Keywords_' . $keyword));
            if (!empty($data))
                return $data;
        }

        $url = '//suggest.taobao.com/sug?code=utf-8&q=' . urlencode($keyword);

        $result = $this->https_request($url);

        $json = json_decode($result, true);

        if (!$json)
            return array();

        $keywords = array();

        foreach ($json['result'] as $key => $value) {
            $keywords[] = $value[0];
        }

        if ($this->config['keywordCache'] > 0) {
            $this->Cache->set(md5('Keywords_' . $keyword), $keywords, $this->config['keywordCache']);
        }
        if ($this->config['isSaveKeyword'] == 1) {
            $this->localKeyword = array_unique(array_merge($this->localKeyword, $keywords));
            file_put_contents(ROOT . 'keywords.txt', implode(PHP_EOL, $this->localKeyword));
        }

        return $keywords;
    }

    /**
     * 获取搜索结果
     * @param string $keyword
     * @return array
     */
    private function getGoods($keyword = '')
    {
        if ($this->config['GoodsCache'] > 0) {
            $data = $this->Cache->get(md5('Goods_' . $keyword));
            if (!empty($data))
                return $data;
        }

        $url = 'https://api.zhetaoke.com:10003/api/api_quanwang.ashx?appkey=' . $this->config['appKey'] . '&page=1&page_size=' . $this->config['pageSize'] . '&sort=new&q=' . urlencode($keyword);

        $result = $this->https_request($url);

        $json = json_decode($result, true);

        if (!$json || $json['status'] != 200)
            return array();

        $datas = array();

        foreach ($json['content'] as $key => $value) {
            $datas[] = array(
                'Url' => $this->getContentUrl($value['tao_id'], $keyword),
                'itemId' => $value['tao_id'],
                'title' => $value['tao_title'],
                'desc' => $value['jianjie'],
                'image' => $value['pict_url'],
            );
        }

        //加入最新搜索关键词
        if (!empty($datas) && !array_search($keyword, $this->newKeyword)) {
            if (count($this->newKeyword) >= 20) {
                unset($this->newKeyword[min(array_keys($this->newKeyword))]);
            }
            $this->newKeyword[time()] = $keyword;
            $this->Cache->set('newKeyword', $this->newKeyword);
        }

        if ($this->config['GoodsCache'] > 0) {
            $this->Cache->set(md5('Goods_' . $keyword), $datas, $this->config['GoodsCache']);
        }

        return $datas;
    }

    /**
     * 获取商品信息
     * @param string $itemId
     * @return object
     */
    private function getContent($itemId = '')
    {
        if ($this->config['ContentCache'] > 0) {
            $data = $this->Cache->get(md5('Content_' . $itemId));
            if (!empty($data))
                return $data;
        }

        $data = array();

        $descUrl = 'https://api.zhetaoke.com:10002/api/api_detail.ashx?appkey=' . $this->config['appKey'] . '&tao_id=' . $itemId;

        for ($i = 0; $i < 5; $i++) {
            $result = json_decode($this->https_request($descUrl));
            if (!empty($result)) {
                break;
            }
        }

        //获取简介内容

        if ($result->status == 200) {
            $info = $result->content;
            $data = $info[0];
        }

        if ($this->config['ContentCache'] > 0) {
            $this->Cache->set(md5('Content_' . $itemId), $data, $this->config['ContentCache']);
        }

        return $data;
    }

    /**
     * Get Curl 请求方法
     * @param $url
     * @return bool|string
     */
    private function https_request($url)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }
}


//缓存类
class Cache
{
    /**
     * 缓存文件存放路径
     */
    public $path;

    /**
     * 缓存存放目录数
     */
    public $max_path;

    /**
     * 最多缓存多少个文件，按需配置，此值越大，GC消耗时间越久
     * 此值对cache命中率影响非常小，几乎可以忽略
     */
    public $max_file;

    /**
     * GC 执行概率 百万分之 *
     */
    public $gc_probality;

    private $basepath;

    public function __construct($path = "cache", $max_path = 100, $max_file = 50000, $gc_probality = 100)
    {
        $this->path = $path;
        $this->max_path = $max_path;
        $this->max_file = $max_file;
        $this->gc_probality = $gc_probality;
        $this->basepath = realpath($this->path) . DIRECTORY_SEPARATOR;
    }

    /**
     * 设置缓存
     *
     * @param string $key 保存的key,操作数据的唯一标识，不可重复
     * @param int|string|array|object|boolean $val 数据内容，可以是int/string/array/object/Boolean 其他没测过，如有需求自行测试
     * @param int $expired 过期时间，不设默认为一年
     * @return bool
     */
    public function set($key, $val, $expired = 1000 * 60 * 60 * 24 * 365)
    {
        if (rand(0, 1000000) < $this->gc_probality)
            $this->gc();

        $key = strval($key);
        $cache_file = $this->_getCacheFile($key);

        $data = unserialize(file_get_contents($cache_file));
        empty($data[$key]) && $data[$key] = array();

        $data[$key]['data'] = $val;
        $data[$key]['expired'] = time() + $expired;

        file_put_contents($cache_file, serialize($data), LOCK_EX) or die("写入文件失败");
        return @touch($cache_file, $data[$key]['expired']);
    }

    /**
     * 获得保存的缓存
     *
     * @param string $key key,操作数据的唯一标识
     * @return null/data
     */
    public function get($key)
    {
        $key = strval($key);
        $cache_file = $this->_getCacheFile($key);
        $val = @file_get_contents($cache_file);

        if (!empty($val)) {
            $val = unserialize($val);
            if (!empty($val) && isset($val[$key])) {
                $data = (array)$val[$key];
                if ($data['expired'] < time()) {
                    $this->delete($key);
                    return null;
                }
                return $data['data'];
            }
        }
        return null;
    }

    /**
     * 开始片段缓存
     * 必须配合endCache使用
     *
     * @param string $key 保存的key,操作数据的唯一标识，不可重复
     * @param int $expired 过期时间，不设默认为一年
     * @return bool
     */
    public function startCache($key, $expired = 31536000)
    {
        $data = $this->get($key);
        if (!empty($data)) {
            print $data;
            return false;
        } else {
            ob_start();
            print $key . "||" . $expired . "filecache_used::]";
            return true;
        }
    }

    /**
     * 结束片段缓存
     *
     * @return bool
     */
    public function endCache()
    {
        $data = ob_get_contents();
        ob_end_clean();
        preg_match("/(.*?)filecache_used::]/is", $data, $key);
        if (empty($key[1])) {
            return false;
        }
        $data = str_replace($key[0], '', $data);
        $t = explode("||", $key[1]);
        $key = $t[0];
        $expired = $t[1];
        $this->set($key, $data, $expired);
        print $data;
        return true;
    }

    /**
     * 删除缓存
     *
     * @param string $key 保存的key,操作数据的唯一标识，不可重复
     * @return bool
     */
    public function delete($key)
    {
        $key = strval($key);
        $cache_file = $this->_getCacheFile($key);

        $data = unserialize(file_get_contents($cache_file));
        unset($data[$key]);
        if (empty($data)) {
            return @unlink($cache_file);
        }
        file_put_contents($cache_file, serialize($data), LOCK_EX);
        return true;
    }

    /**
     * 缓存回收机制
     * 遍历所有缓存文件，删除已过期文件，如果缓存文件存在不止一个缓存数据，照删不务……
     * TODO 这里之前用hashtable做了数据索引，GC时间会比遍历快30%左右，但是会拖慢set和get的时间，据我测试set会慢一倍！
     *
     * @param string $path 缓存目录
     * @return void
     */
    public function gc($path = null)
    {
        if ($path === null)
            $path = $this->basepath;
        if (($handle = opendir($path)) === false)
            return;
        while (($file = readdir($handle)) !== false) {
            if ($file[0] === '.')
                continue;
            $fullPath = $path . DIRECTORY_SEPARATOR . $file;
            if (is_dir($fullPath)) {
                $this->gc($fullPath);
            } elseif (@filemtime($fullPath) < time()) {
                @unlink($fullPath);
            }
        }
        closedir($handle);
    }


    private function _getCacheFile($key)
    {
        $hash = $this->hash32($key);
        $path = $this->basepath . $this->_getPathName($hash);
        $file = $path . DIRECTORY_SEPARATOR . $this->_getCacheFileName($hash);
        if (!file_exists($path)) {
            mkdir($path, 0777);
        }
        if (!file_exists($file)) {
            $handler = fopen($file, 'w');
            fclose($handler);
        }
        return $file;
    }

    private function _getPathName($hash)
    {
        return $hash % $this->max_path;
    }

    private function _getCacheFileName($hash)
    {
        return $hash % $this->max_file;
    }

    private function hash32($str)
    {
        return crc32($str) >> 16 & 0x7FFFFFFF;
    }
}